package server;

import java.net.*;
import java.security.InvalidKeyException;
import java.util.Arrays;
import java.io.*;

import client.data.KeyPairPair;


import packets.Packet;
import packets.client.PublicKeyPair;
import packets.server.Certificate;


import encription.SecurityHandler;
import exceptions.UnverifiedMessageException;

/**
 * A thread that communicates with clients and handles their messages
 */
public class MultiServerThread extends Thread {
	private Socket _socket = null;
	private KeyPairPair _serverKey = null;
	private InputStream _clientIn = null;
	private OutputStream _clientOut = null;
	private CAProtocol _protocol = null;
	private SharedStuff _stuff = null;

	/**
	 * Creates an instance of {@link MultiServerThread}
	 * @param socket the socket which the clients connects from
	 * @param stuff server's shared data
	 * @param serverkey server's keys for encryption/signing
	 */
	public MultiServerThread(Socket socket, SharedStuff stuff , KeyPairPair serverkey) {
		this._socket = socket;
		this._serverKey = serverkey;
		this._protocol = new CAProtocol(new InetSocketAddress(socket.getInetAddress(),socket.getPort()) , stuff, _serverKey.getSigningKey());
		this._stuff = stuff;
	}

	/**
	 * starts the {@link MultiServerThread}
	 */
	public void run() {
		try {
			_clientOut = new BufferedOutputStream(_socket.getOutputStream());	//initialize output stream
			_clientOut.flush();
			_clientIn = new BufferedInputStream(_socket.getInputStream());		//initialize input stream

			//receive first message from the client , which was encrypted using the server public keys
			byte[] content = new byte[8192];
			int msgSize =  _clientIn.read(content);
			byte[] message = Arrays.copyOfRange(content, 0, (msgSize-128));			//split message to data and signature
			byte[] signature = Arrays.copyOfRange(content, msgSize-128, msgSize);

			//retrieve the Packet that the user sent
			Packet p = (Packet)SecurityHandler.getObject(SecurityHandler.decrypt(message, _serverKey.getDecryptionKey(), message.length));

			int clientId;
			PublicKeyPair clientKeys = null;

			if (p.getType()==Packet.PUBLIC_KEYS){		//user sent his public keys , means that he wants to register
				clientKeys = p.getKeysContent();		//get user's keys
				if (!_stuff.isLegalKey(clientKeys)){
					SecurityHandler.sendEncryptedMessage(new Packet("keyincrl"), clientKeys.getEncryptionKey(), _serverKey.getSigningKey(), _clientOut);
					throw new InvalidKeyException("CRL: not a valid key");
				}

				//verifying the user's signature
				if (!SecurityHandler.verify(message, signature, clientKeys.getSigkey())){
					SecurityHandler.sendEncryptedMessage(new Packet("invalidsignature"), clientKeys.getEncryptionKey(), _serverKey.getSigningKey(), _clientOut);
					throw new UnverifiedMessageException("Could not verify client signature on initial contact");
				}
				//create a Certificate for the client and send it
				clientId = _stuff.getNewClientId();		//assign new ID to the user
				Certificate certificate = new Certificate(clientId,clientKeys.getEncryptionKey(), _socket.getInetAddress(), _socket.getPort() , _serverKey.getSigningKey());
				SecurityHandler.sendEncryptedMessage(new Packet(certificate), clientKeys.getEncryptionKey(), _serverKey.getSigningKey(), _clientOut);

				//receive registration request from the client
				Packet packet = SecurityHandler.getMessage(clientKeys.getSigkey(), _serverKey.getDecryptionKey(), _clientIn);
				//attempt register the client and save him in the database
				packet = _protocol.register(clientId, packet.getRegistrationContent(), clientKeys, certificate);
				//send the server's reply for the registration request back to the client
				SecurityHandler.sendEncryptedMessage(packet, clientKeys.getEncryptionKey(), _serverKey.getSigningKey(), _clientOut);

			} else if(p.getType()==Packet.LOGIN) {			//user sent a login request
				int id = p.getLoginContent().getId();		//retrieve user id from the request details
				clientKeys = _stuff.getPublicKeyPair(id);	//retrieve the client keys from the database

				if (clientKeys == null){
					//sends a unencrypted message to the client since we don't know his encryption key
					SecurityHandler.sendEncryptedMessage(new Packet("hacker!!!!! gtho!!!"), null, _serverKey.getSigningKey(), _clientOut);
					throw new InvalidKeyException("client does not exist");
				} else {
					//verifying the client's signature
					if (!SecurityHandler.verify(message, signature, clientKeys.getSigkey())){
						SecurityHandler.sendEncryptedMessage(new Packet("hacker!!!!! gtho!!!"), null, _serverKey.getSigningKey(), _clientOut);
						throw new UnverifiedMessageException("Could not verify client signature when logging in");
					}
					//send the reply for the login request to the client
					Packet packet = _protocol.handlePacketMessage(p);
					SecurityHandler.sendEncryptedMessage(packet, clientKeys.getEncryptionKey(), _serverKey.getSigningKey(), _clientOut);
				}
			}
			while(true){	//accepts messages from the client and deals with them , until the channel is closed
				//receive a message
				Packet packet = SecurityHandler.getMessage(clientKeys.getSigkey(), _serverKey.getDecryptionKey(), _clientIn);
				System.out.print("got request: ");
				switch(packet.getType()){
					case Packet.PUBLIC_KEYS:
						System.out.println("PUBLIC_KEYS");
						break;
					case Packet.STRING_MSG:
						System.out.println(packet.getStringContent());
						break;
					case Packet.CERTIFICATE:
						System.out.println("CERTIFICATE");
						break;
					case Packet.USERDETAILS:
						System.out.println("USERDETAILS");
						break;
					case Packet.USERLIST:
						System.out.println("USERLIST");
						break;
					case Packet.REGISTRATION:
						System.out.println("REGISTRATION");
						break;
					case Packet.LOGIN:
						System.out.println("LOGIN");
						break;
					case Packet.DELETE:
						System.out.println("DELETE");
						break;
					case Packet.LOGOUT:
						System.out.println("LOGOUT");
						break;
					case Packet.WHOSONLINE:
						System.out.println("WHOSONLINE");
						break;
					case Packet.ALLUSERS:
						System.out.println("ALLUSERS");
						break;
					case Packet.CHAT:
						System.out.println("CHAT");
						break;
					case Packet.DETAILS:
						System.out.println("DETAILS");
						break;
					case Packet.OK:
						System.out.println("OK");
						break;
					case Packet.VERIFY:
						System.out.println("VERIFY");
						break;
					default:
						System.out.println("BAD_THING");
				}
				//generate a reply
				Packet reply = _protocol.handlePacketMessage(packet);
				//System.out.println("generated reply");
				//encrypt the reply with the client keys and send it through
				SecurityHandler.sendEncryptedMessage(reply , clientKeys.getEncryptionKey() , _serverKey.getSigningKey(), _clientOut);
				//System.out.println("sent back response.");
			}

			//in.close();
			//_socket.close();
		} catch (IllegalArgumentException e) {
			_stuff.logoutUser(new InetSocketAddress(_socket.getInetAddress(), _socket.getPort()));
			System.out.println("Connection closed by client");
			//e.printStackTrace();
		} catch (IOException e) {
			_stuff.logoutUser(new InetSocketAddress(_socket.getInetAddress(), _socket.getPort()));
			System.out.println("Server encountered an IOException - possibly client closed his socket");
			//e.printStackTrace();
		} catch (Exception e) {
			//log out the user in case he was logged in
			_stuff.logoutUser(new InetSocketAddress(_socket.getInetAddress(), _socket.getPort()));
			SecurityHandler.catchException("communicating with a client", e, false);
		}
	}
}
